var events = require('events');
var hashTable = require('../../../common/hashtable.js');
var appUtility = require('../../../common/apputility.js');
var net = require('net');
var util = require('util');
var fs = require("fs");
var tls = require('tls');

var MODNAME = 'TcpStack';

var MODE = 'SERVER';
/*This is the main constructor function */
var Stack = function(pMngrObj, pParamsObj) {
	if (!(this instanceof Stack)) return new Stack(pParamsObj);
	events.EventEmitter.call(this);
	
	this._Clients = hashTable.create();
	this._NewClientId = 1000;
	this._Messages = hashTable.create();
	this._Server = null;
};

util.inherits(Stack, events.EventEmitter);

Stack.prototype.onInit = function (pMngrObj) {
	
	var self = this;
	var conf = self._Config.PARAMS;
	
	// Assign role if it is defined
	var propName = "ROLE";
	if (conf[propName]) {
		MODE = conf[propName] = conf[propName].toUpperCase();
		self.logger.log(MODNAME, 'INFO_LEVEL_4', 'Current Mode: ' + MODE);
	}
	
	if ("SERVER" === MODE) { // if the role is as server
		if(conf.USETLS === true) {
			//TODO:
		} else {	
			self._Server = net.createServer(self._OnNewConnection.bind(self));		
			self._Server.listen(conf.PORT, conf.HOST, function(pObj) {				
				 self.logger.log(MODNAME, 'INFO_LEVEL_3','['+ self._Config.NAME +']server started - listening on port ' + conf.PORT);
				});
		}
	} else if ("CLIENT" === MODE) { // if the role is as client
		this.tryConnect();
	} else {
		self.logger.log(MODNAME, 'ERROR', 'Invalid Config Property [ROLE]');
		return false;
	}

	if('JSON' === conf.MSGTYPE) {
		self.procClientRcvdData = self.procJsonMessage;
	} else {
		self.procClientRcvdData = self.procDataMessage;
	}
	
	return true;
};

Stack.prototype.onExit = function (pMngrObj) {
	
};

Stack.prototype.onSubscribe = function (pMngrObj) {
	
	pMngrObj.subscribeEvent(this, 'TCP.SENDMSG', null, 0);
	return true;
};

Stack.prototype.onEvent = function (pEvent) {
	var eventName = pEvent.getEventName();
	this.logger.log(MODNAME, 'INFO_LEVEL_3', 'onEvent Handler received event['+eventName+'] '); 
	this.logger.log(MODNAME, 'INFO_LEVEL_3', 'event payload '+ JSON.stringify(pEvent));	
	this.sendMessage(pEvent);
	this.logger.log(MODNAME, 'INFO_LEVEL_3', 'onEvent Handler Completed processing... event: '+eventName);
};


Stack.prototype.procJsonMessage = function(socket, data) {
	//console.log(data.toString('utf8', 4, data.length))
	//var jsonObj = JSON.parse(data.toString('utf8', 4, data.length));
	//console.log(data.toString('utf8', 4, data.length));
	//return;
	//console.log('Rcvd Data - length: '+ data.length);
	var jsonObj = JSON.parse(data.toString('utf8', 4, data.length));
		
	
	
	var eventObj = socket._StackObj.createNewEvent('TCP.ONMSGRCVD', 'REQUEST');
	//eventObj.setEventKey(appUtility.getKey());
	//eventObj.setStackName(socket._StackObj._Config.NAME);
	//eventObj.setClientId(socket.CLIENTID || 0);
	eventObj.setRespEventName('TCP.SENDMSG');
	eventObj.setPayload(jsonObj);
	
	//console.log(eventObj);
	//var evt = socket._StackObj._Config.NAME+'_'+socket._StackObj._Events[0];
	//socket._StackObj.emit(evt, socket._StackObj, eventObj);
	socket._StackObj._Messages.setItem(eventObj.getEventKey(), {'CLIENTID': socket.CLIENTID});
	socket._StackObj.sendEvent(eventObj);
};

Stack.prototype.procDataMessage = function(socket, data) {
/*	var msg = data.toString('utf8', 4, data.length);
	
	var eventObj = socket._StackObj.createNewEvent();
	eventObj.header.eventname = 'TCP.REQUEST';
	eventObj.header.eventtype = 'REQUEST';
	eventObj.header.sdpkey = appUtility.getKey();
	eventObj.header.stackname = socket._StackObj._Config.NAME;
	eventObj.header.clientid = socket.CLIENTID || 0;
	eventObj.header.respeventname = 'STACK';
	eventObj.data.params.message = msg;
	
	//console.log(msg);
	//var evt = socket._StackObj._Config.NAME+'_'+socket._StackObj._Events[0];
	//socket._StackObj.emit(evt, socket._StackObj, eventObj);
	
	socket._StackObj.sendEvent(socket._StackObj._Events[0], eventObj);
	*/
};


Stack.prototype.sendMessage = function(pEvent) {
	var socket = null;
	if ("SERVER" === MODE) {
		var msgInfo = this._Messages.getItem(pEvent.getEventKey());
		if (!msgInfo) {
			this.logger.log(MODNAME, 'INFO_LEVEL_4', '[eventkey: '+pEvent.getEventKey()+'] Failed to get client information for the event');
			return;
		} 

		var clientObj = this._Clients.getItem(msgInfo.CLIENTID);
		if (clientObj) {
			socket = clientObj.SOCKET;
			this.logger.log(MODNAME, 'INFO_LEVEL_4', '[eventkey: '+pEvent.getEventKey()+'] retrieved client information - id: '+msgInfo.CLIENTID);
		}
	} else {
		socket = this._Server;
	}
	
	if(socket) {
		var msgObj = pEvent.getPayload();
		var data = JSON.stringify(msgObj);
		var msgBuff = new Buffer(4 + data.length);

		msgBuff.writeUInt32BE(msgBuff.length, 0);
		msgBuff.write(data, 4);
		//socket.write(msgBuff);
		socket.write(msgBuff.slice(0,4));
		socket.write(msgBuff.slice(4));
		this.logger.log(MODNAME, 'INFO_LEVEL_4', 'packet sent, length = ' + msgBuff.length);
		//delete data;
	}
	else {
		this.logger.log(MODNAME, 'WARNING', 'Invalid socket object');
	}

};

///////////////////////////////////////////////////////////////////////////////////////////////////
Stack.prototype.tryConnect = function() {
	var self = this;
	var conf = self._Config.PARAMS;
	setTimeout(function () {
		self.logger.log(MODNAME, 'INFO_LEVEL_4',"Trying to connect with [" + conf.HOST + ":" + conf.PORT + "]...");
		var socket = net.createConnection(15000,'127.0.0.1', function () {
			self.logger.log(MODNAME, 'INFO_LEVEL_4',"Connectionn established with [" + conf.HOST + ":" + conf.PORT + "]...");			
		});

		socket.on('data', self._OnSocketDataRcvd.bind(socket));
		socket.on('end', self._OnSocketConnClosed.bind(socket));
		socket.on('error', self._OnSocketConnError.bind(socket));

		socket._StackObj = self;
		socket.header = {};
		socket.header.bytesReadCount = 0;
		socket.header.buff = new Buffer(4);

		socket.msg = {};
		socket.msg.expectedMsgSize = 0;
		socket.msg.bytesReadCount = 0;
		
		self._Server = socket;

	}, 5000);

//	var err = new Error();
//	console.log("Error :" + err.stack);
//	console.log("Error :" + err.lineNumber);

//	console.trace("Test");
};


Stack.prototype._OnNewConnection = function(socket) {
	var self = this;
	
	socket.header = {};
	socket.header.bytesReadCount = 0;
	socket.header.buff = new Buffer(4);

	socket.msg = {};
	socket.msg.expectedMsgSize = 0;
	socket.msg.bytesReadCount = 0;

	var clientId = ++this._NewClientId;
	socket._StackObj = this;
	socket.CLIENTID = clientId;
	socket.on('data', self._OnSocketDataRcvd.bind(socket));
	socket.on('end', self._OnSocketConnClosed.bind(socket));
	socket.on('error', self._OnSocketConnError.bind(socket));
	
	var clientObj = {			
			'SOCKET': socket,			
	};
	this._Clients.setItem(clientId, clientObj);
	this.logger.log(MODNAME, 'INFO_LEVEL_4','['+ this._Config.NAME +'] server - New Client [ID: '+clientId+'] connected ');
		
};


Stack.prototype._OnSocketConnClosed = function(pObj) {
	var stackObj = this._StackObj;
	var clientId = this.CLIENTID;
	stackObj.logger.log(MODNAME, 'INFO_LEVEL_4','Socket connection is terminated ');
	stackObj._Clients.removeItem(clientId);
};

Stack.prototype._OnSocketConnError = function(pObj) {
	var stackObj = this._StackObj;
	stackObj.logger.log(MODNAME, 'ERROR','Connection Failed '+ pObj.toString());
	
	if ("CLIENT" === MODE) {
		stackObj.tryConnect();
	}
};

Stack.prototype._OnSocketDataRcvd = function(pObj) {
	var data = pObj;
	var stackObj = this._StackObj;
	//console.log('Received: '+ data.length);// +', '+ data);
	stackObj.logger.log(MODNAME, 'INFO_LEVEL_4','Received Data [Length:'+data.length+'] from [ClientId: '+this.CLIENTID+']');
	//stackObj.procClientRcvdData(this, data);
	
	console.log("Data received. Isbuffer = " + Buffer.isBuffer(data));
	console.log("Data received. buffer length = " + data.length);

	var processedRcvdBytes = 0;
	var rcvdBuffLen = data.length;

	while(processedRcvdBytes < rcvdBuffLen)
	{
		var remainBytesToProcess = rcvdBuffLen - processedRcvdBytes;

		console.log("Remaining bytes to process from received buff : " + remainBytesToProcess);

		if(this.header.bytesReadCount < 4)
		{
			var hdrBytesToRead = 4 - this.header.bytesReadCount;
			
			console.log("Remaining header bytes to read : " + hdrBytesToRead);
			if(remainBytesToProcess < hdrBytesToRead)
			{			
				data.copy(this.header.buff, this.header.bytesReadCount, processedRcvdBytes, rcvdBuffLen);
				this.header.bytesReadCount += remainBytesToProcess;
				processedRcvdBytes += remainBytesToProcess;
			}
			else
			{	
				data.copy(this.header.buff, this.header.bytesReadCount, processedRcvdBytes, processedRcvdBytes + hdrBytesToRead);
				this.header.bytesReadCount += hdrBytesToRead;
				processedRcvdBytes += hdrBytesToRead;

				this.msg.expectedMsgSize = this.header.buff.readUInt32BE(0);
				console.log("Expected msg size : " + this.msg.expectedMsgSize);
			
				this.msg.buff = new Buffer(this.msg.expectedMsgSize);
				this.header.buff.copy(this.msg.buff, 0, 0, 4);
				this.msg.bytesReadCount = 4;
			}
		}
		else
		{
			var msgBytesToRead = this.msg.expectedMsgSize - this.msg.bytesReadCount;
			if(remainBytesToProcess < msgBytesToRead)
			{
				data.copy(this.msg.buff, this.msg.bytesReadCount, processedRcvdBytes, rcvdBuffLen);
				this.msg.bytesReadCount += remainBytesToProcess;
				processedRcvdBytes += remainBytesToProcess;
			}
			else
			{			
				data.copy(this.msg.buff, this.msg.bytesReadCount, processedRcvdBytes, processedRcvdBytes + msgBytesToRead);
				this.msg.bytesReadCount += msgBytesToRead;
				processedRcvdBytes += msgBytesToRead;

				this._StackObj.procClientRcvdData(this, this.msg.buff);

				this.header.bytesReadCount = 0;

				this.msg.expectedMsgSize = 0;
				this.msg.bytesReadCount = 0;
				delete this.msg.buff;
			}
		}
	}
	
};



module.exports.create = function(pObj1, pObj2) { return new Stack(pObj1, pObj2); };



//var obj = {'PORT': 15000, 'HOST': '127.0.0.1', 'USETLS': 0, 'MSGTYPE': 'JSON'};
//var StackObj = new Stack(obj);


